function clearObjects() {
    objects = [];
    dynamic_objects = [];
    static_objects = [];
    hud_objects = [];
}


var levelNum = 0;

var water;

function createBounds(width, height) {
    addStatic(makeRect(-100, -100, 0, height + 100).makeInvisible());
    addStatic(makeRect(width, -100, width + 100, height + 100).makeInvisible());

	addStatic(water);

    addTrigger(makeRect(-100, height + 20, width + 100, height + 100).makeInvisible(), killPlayer);
    addTrigger(makeRect(-100, -20, width + 100, -100).makeInvisible(), killPlayer);
}

function killPlayer() {
	shouldReset=true;
	playerDying = true;
	countDown = 2000;
}

var levels = [ ];

function functionName(func) {
	var s = func.toString();
	s = s.replace("function", "");
	s = s.replace(/\(.*/, "");
	s = s.split("\n")[0];
	return s;
}

function setupWorld() {
    clearObjects();
	gravityInverted = false;
	console.log("Loading level: " + levelNum + " (" + functionName(levels[levelNum]) + " )");
	levels[levelNum]();
}

function nextLevel() {
	if(!shouldReset){
		player.glObject.atEnd = true;
		player.move = function() { };
		player.will_collide_line = function() { return false; };
		levelNum++;
		if (levelNum == levels.length) levelNum = 0;
		shouldReset=true;
		countDown = 500;
	}
}

function addObject(obj) {
    objects.push(obj);
}

function addStatic(obj) {
    addObject(obj);
    static_objects.push(obj);
}

function addDynamic(obj) {
    addObject(obj);
    dynamic_objects.push(obj);
}

function addTrigger(obj,triggerFunc) {
	obj.trigger = triggerFunc;
    obj.is_solid = function() { return false; };
	addObject(obj);
	static_objects.push(obj);
}

function resetLevek(obj) {
	shouldReset = true;
}

function endLevel(obj) {
    endLevelSound.play();
	nextLevel();
}

function addPlayer(obj) {
    player = obj;
    player.shouldJump = false;
    addDynamic(obj);
}

function distanceToLineSegment(p0, l0, l1) {
    var pdist = Math.min(dist(p0, l0), dist(p0, l1));
    var dota = dot(sub(l1, l0), sub(p0, l0));
    if (dota < 0.0001) {
        return pdist;
    }

    var dotb = dot(sub(l0, l1), sub(p0, l1));
    if (dotb < 0.0001) {
        return pdist;
    }

    var ret = Math.abs(det(sub(l1, l0), sub(p0, l0)) / dist(l0, l1));
    return ret;
}

function circleCollideLine(pt0, pt1) {
    if (distanceToLineSegment(add(this.p, this.d), pt0, pt1) < this.r) return true;
}

function drawCircle() {
    context.fillStyle = "#000000";
    context.beginPath();
    context.arc(this.p[0], this.p[1], this.r, 0, Math.PI*2, true); 
    context.closePath();
    context.fill();
}

function drawRect() {
    context.strokeStyle = "#000000";
    context.fillStyle = this.fillStyle;
	if (this.isHazardous == 2) {
		var p = this.p;
		var d = this.p1;
		context.beginPath();
		context.moveTo(p[0], p[1] + d[1] / 2);
		context.lineTo(p[0] + d[0] / 2, p[1]);
		context.lineTo(p[0] + d[0], p[1] + d[1] / 2);
		context.lineTo(p[0] + d[0] / 2, p[1] + d[1]);
		context.closePath();
		context.fill();
	} else {
		context.fillRect(this.p[0], this.p[1], this.p1[0], this.p1[1]);
		context.strokeRect(this.p[0], this.p[1], this.p1[0], this.p1[1]);
	}
}

function drawPoly() {
    context.strokeStyle = "#000000";
    context.fillStyle = this.fillStyle;
	context.moveTo(this.p[0] + this.boundary[0][0], this.p[1] + this.boundary[0][1]);
    for (var i = 1; i < this.boundary.length; i++) {
        context.lineTo(this.p[0] + this.boundary[i][0], this.p[1] + this.boundary[i][1]);
    }
    context.fill();
    context.stroke();
}

function drawGlObject() {
    this.glObject.pos = vec3.create([this.p[0], -this.p[1], 0.0]);
    //this.glObject.pos = vec3.create([0.0, 0.0, 0.0]);
    this.glObject.draw(camera);
}

function makeWater(x, y, w, h) {
    var ret = makeRectX(x, y, w, h, 2);
    ret.glObject = SegmentedRectModel(h, w, 20.5, 200, 1, 10);
    ret.glObject.shader = waterShaderProgram;
	ret.glObject.color = [0.1, 0.3, 0.9, 1.0];
    ret.drawGl = drawGlObject;
    ret.is_solid = function() { return false; };
	ret.glObject.ignoreCollision = function(decomp) {
		console.log(this.uCollisions[2]);
		console.log(" " + this.uTime);
		return ignoreCollision(decomp) ||
			(this.uTime - this.uCollisions[2] < 2000 &&
			 this.uCollisions[3] > 0.1);
	};
    return ret;
}

function makeRamp(x, y, r, func) {
    var ret = new PhysicsObject();
	ret.p = [x, y];
	ret.d = [0, 0];
	ret.dim = [l, -h];
	ret.draw = drawPoly;

	ret.stickiness = 100;

	ret.boundary = function () {
		var ret = [];
		for (var i = 0; i <= l; i++) {
			ret.push([i, -h * func(i/l)]);
		}
		ret.push([l, 0], [0, 0]);
		return reverse(ret);
	}();
	ret.fillStyle = "#800517";

	ret.glObject = new RampModel();
    ret.glObject.shader = shaderProgram;
    ret.glObject.angle = 0;
	ret.glObject.create(func);
	ret.drawGl = drawGlObject;

	return ret;
}

function makeCircle(x, y, r) {
    var ret = new PhysicsObject();
    ret.p = [x, y];
    ret.d = [0, 0];
    ret.r = r;
    ret.glObject = new HamsterBallModel();
    ret.glObject.radius = r;
    ret.glObject.create();
    ret.glObject.initBuffers();
    ret.glObject.shader = shaderProgram;
    ret.draw = drawCircle;
    ret.drawGl = drawGlObject;
    ret.will_collide_line_internal = circleCollideLine;
	
	ret.update = function(){
		this.glObject.stuck = this.stuck;
		ret.move();
	}
	
    ret.boundary = function() {
        var poly = [];
        var N = 36;
        for (var i = 0; i <= N; i++) {
            poly[i] = scale(r, [Math.cos(Math.PI * i / N * 2), -Math.sin(Math.PI * i / N * 2)]);
        }
        return poly;
    }();
    return ret;
}

function rotateAll(poly, rot) {
	for (var i = 0; i < poly.length; i++) {
		poly[i] = rotate(poly[i], rot);
	}
}

function CircleRamp(x, y, l, h) {
	var ret = new PhysicsObject();
	ret.p = [x, y];
	ret.d = [0, 0];
	ret.dim = [l, -h];
	ret.draw = drawPoly;

	ret.stickiness = 100;

	ret.rotate = function(angle) {
        ret.glObject.angle = -angle;
		rotateAll(this.boundary, angle);
		this.p = add(this.p, add(scale(0.5, this.dim), scale(0.5, rotate(this.dim, Math.PI + angle))));
		return this;
	}

	ret.boundary = function () {
		var ret = [];
		for (var i = 0; i <= l; i++) {
			ret.push([i, -h * (1 - Math.sqrt(1 - i * i / l / l))]);
		}
		ret.push([l, 0], [0, 0]);
		return reverse(ret);
	}();
	ret.fillStyle = "#800517";

	ret.glObject = new RampModel();
    ret.glObject.shader = shaderProgram;
    ret.glObject.angle = 0;
	ret.glObject.create(function(x){return 1 - Math.sqrt(1 - x * x);}, 30, l, h);
	ret.drawGl = drawGlObject;

	return ret;
}

function ParabolaRamp(x, y, l, h) {
    var ret = new PhysicsObject();
    ret.p = [x, y];
    ret.d = [0, 0];
    ret.draw = drawPoly;

    ret.boundary = function () {
        var ret = [];
        for (var i = 0; i <= l; i++) {
            ret.push([i, -h * i * i / l / l]);
        }
        ret.push([l, 0], [0, 0]);
        return reverse(ret);
    }();
    ret.fillStyle = "#800517";
	ret.glObject = new RampModel();
    ret.glObject.angle = 0;
    ret.glObject.shader = shaderProgram;
	ret.glObject.create(function(x){return x * x;}, l / 4, l, h);
	ret.drawGl = drawGlObject;
    return ret;
}

function reverse(vec) {
    var ret = [];
    for (var i = 0; i < vec.length; i++) {
        ret[i] = vec[vec.length - i - 1];
    }
    return ret;
}

function makeRectX(x, y, w, h, is) {
    return makeRect(x, y, x + w, y + h, is);
}

function makeRect(l, t, r, b, is) {
    var ret = new PhysicsObject();
    ret.isHazardous = is; // 0 if regular obstacle, 1 if ramp obstacle
                          // 2 if hazard rectangle, 3 if goal rectangle
    var depth = 25;
    if (ret.isHazardous == 0) {
        ret.fillStyle = "#556677";
    } else if (ret.isHazardous == 1) {
        ret.fillStyle = "#2244ff";
    } else if (ret.isHazardous == 2) {
        depth = 24;
        ret.fillStyle = "#800517";
    } else if (ret.isHazardous == 3) {
        ret.l_goal = l;
        ret.t_goal = t;
        ret.r_goal = r;
        ret.b_goal = b;
        ret.fillStyle = "#33ffee";
        depth = 18;
    } else {
        ret.fillStyle = "#000000";
        //document.write("invalid object state");
    }
    r -= l;
    b -= t;
    ret.p = [l, t];
    ret.p1 = [r, b];
    ret.draw = drawRect;
    ret.boundary = function() { return [[0, 0], [0, b], [r, b], [r, 0], [0, 0]]; }();
    ret.glObject = new RectModel(b, r, depth);
    ret.glObject.shader = objectShaderProgram;
    ret.drawGl = drawGlObject;
    return ret;
}

function shouldFlash(period, tm) {
    return (tm % period < period / 2);
}

function keepTime() {
    this.tm += 10;
}
function makeTimekeeper(obj) {
    obj.update = function() {
        this.tm += timeStep;
    };
    obj.tm = 0;
}

function addTimer(x, y) {
    var tm = new HudObject();
    tm.p = [x, y];
    tm.drawTo = function(target) {
        target.context.font = "16px sans-serif";
        target.context.fillText("Time: " + formatTime(this.tm), this.p[0], this.p[1]);
    }
    makeTimekeeper(tm);
    objects.push(tm);
}


function addHint(str, x, y) {
    var hint = new HudObject();
    hint.str = str;
    hint.p = [x, y];
    hint.drawTo = function(target) {
        if (shouldFlash(1000, this.tm)) {
            target.context.font = "30px sans-serif";
            target.context.fillText(this.str, this.p[0], this.p[1]);
        }
    };
    makeTimekeeper(hint);
    objects.push(hint);
}





